cluster: decode_deterministic 三值 enum + honest gate 抽 helper + CLI 值域 (#118, #120) - #130
Conversation
…onest gate (#118, #120) #118 settled the representation: the field records WHAT WE KNOW, not what the backend is. Bool? overloaded nil to mean both 'legacy row' and 'this backend ignores the flag'. DecodeDeterminism separates them — deterministic-enforced flag consumed, on fallback-enabled flag consumed, off flag-not-consumed backend ignores the flag, and this makes NO claim about whether its decode is actually reproducible Legacy rows stay on field-absence, which is no longer overloaded. An unknown wire value throws DecodingError.dataCorrupted (loud), while absent and explicit null both decode to nil. #120 item 1: the honest gate moves out of the benchmark loop into DecodeDeterminism.forBackend(_:flagRequested:), a pure non-optional function next to flagConsumingBackends. It lives on the enum because the rule IS the meaning of the cases — adding a flag-consuming backend is now one edit in the file that documents the invariant it can violate. The 'never lie' property is finally unit-testable without running a real backend. #120 item 2: --run-kind is a RunKind ExpressibleByArgument (mirroring the existing HallucinationFilterMode precedent), so a typo fails at the CLI boundary with exit 64 instead of surviving to bench CI. MeasurementRow.runKind stays String? — the type constrains the entry point, not the stored row. Both value sets carry a keep-in-sync note pointing at bench's validate_measurements.py; there is no mechanical link (the #120 Residue). bestASR-bench side landed as c93a70e. Refs #118 Refs #120
…l shape (#130) Seven convergent verify findings, all in-scope by the DA's boundary (this PR may fix text it wrote and text it made false, plus tests in the file it created — not what it merely made visible): - CHANGELOG cited bestASR-bench@e728f1a as the authority for the enum. That commit's validator enforces isinstance(..., bool) — it REJECTS every value the paragraph introduces. Now c93a70e. Two legs caught it independently; shipped text that contradicts itself is the worst kind. - The compat sentence covered only an ABSENT field. It now also states what happens to the boolean this same unreleased entry used to describe: rejected, not coerced, with the evidence that nothing ever wrote one. - #120 was missing from the CHANGELOG entirely, though --run-kind bogus failing at parse is user-visible. Added, including WHY the stored field stays a string. - The #109 paragraph said --decode-deterministic is 'forwarded elsewhere', which now reads as contradicting the #118 paragraph beside it. It is a silent no-op elsewhere — that is the whole reason flag-not-consumed exists. - deterministicEnforced's doc claimed 'the same audio yields the same text'. An over-claim inside a PR about not over-claiming — disabling temperature fallback does not establish byte-identical output across runtime, hardware or model revision. It now says what was observed: the setting was enforced. - 'hyphenated snake-case' is kebab-case. - RunKind's doc claimed to be the Swift side's single source of truth. It is not: the row types stay String?, so a library caller can still store anything and bench CI stays the backstop. The comment now states that scope AND the reason it differs from decode_deterministic — that field's domain is derived from this repo's backend roster, while run_kind is human-typed provenance whose vocabulary is already incomplete (the regression gate benchmarks with no --run-kind at all). Closing it at the row type would trade a loud CI failure for a silently dropped row. Plus two tests: the pre-#118 boolean shape is now locked as REJECTED (a census expires, a test does not — and main still contains the bool-writer today), and SubmissionRow's absent-not-null contract is asserted on the type that actually crosses to the bench repo, not only via MeasurementRow. Forward-compat is now documented on the enum: adding a case is breaking for older readers AND fails invisibly, because both consumers turn the throw into a quiet drop. Mechanism-level fix tracked in #131. Refs #118 Refs #120
The flag-not-consumed row listed 'Apple', which is not a backend in ModelGrid — Apple Speech is still an open proposal (bestASR#121). Name what exists: mlx-audio (silent no-op) and the Fluid family (no such knob). Refs PsychQuant/bestASR#130
Verify Report — PR #130 (#118, #120)Enginemanual fan-out — 3 lens Agents(logic / requirements / regression,model: opus)
AggregatePASS — 0 blocking;7 個 in-scope fix 已於 round-1( 要求覆蓋率8 / 8 FULLY(#118 契約 5 項 + #120 三項;requirements lens 逐項對照 Decision comment)。 這個改動是「刻意破壞相容」——安全性用六種方法查證舊
DA 另加一條: Findings(合併後,Fix 欄 = round-1)
DA 的兩項裁決(採納)Q2 — Q5 — 三個值的命名(使用者拍板):裁定保留,且理由優於 orchestrator 的: Scope Check無超出 #118/#120 範圍。Round-1 的 7 項修復嚴格遵守 DA 的邊界判準: Process Gaps無。Codex leg 正常完成。 Follow-up Findings Filed
誠實的範圍聲明本 PR 交付的是 |
Git auto-merged cleanly but silently mis-filed the CHANGELOG: it produced a duplicate '### Fixed' heading and moved the #109 (release sweep) and #111 (provenance fields) entries — both Added items — underneath it. Restructured so Unreleased has one Added (#121, #109, #111), one Fixed (#121 x6, #117, #116/#115, #112) and one Changed (#107). Verified against the post-merge main: 445 tests / 88 suites pass, which covers the semantic interaction CI could not see (#130 changed decodeDeterministic's type after this branch's CI last ran). Refs #121
* feat: Apple Speech (SpeechAnalyzer) backend (#121) The OS-native ASR backend: Speech.framework's SpeechAnalyzer / SpeechTranscriber, macOS 26+. No dependency, no weight download, no supply-chain pin — the model ships with the OS, so its version is the OS version. Registered unconditionally in CommandCore.live(); the struct carries no @available so it is constructible at the package's macOS 14 deployment target, and every macOS-26-only call sits behind an @available helper. isAvailable() is a pure version gate and never touches the asset inventory, which keeps list-backends cheap. Three probed behaviors drive the design: - transcriber.results is an AsyncSequence that must be consumed before audio is fed, or results are lost. - A missing locale asset reports "Audio format is not supported" — a misleading message. The engine checks installedLocales and downloads on demand instead of diagnosing from the error text. - The framework has no determinism knob, so decode_deterministic records flag-not-consumed rather than claiming enforcement (#118). Language is required rather than guessed: SpeechTranscriber has no auto-detect mode, and a ja file decoded under en_US yields garbage that would enter the benchmark evidence base silently. zh maps to zh_TW to match the project's Common Voice zh-TW corpora. Per-segment confidence is derived as the minimum over Apple's per-run values, so a confident run cannot mask a weak one inside the same cue. Also fixes a defect this backend exposed: Router's backend-membership list omitted apple-speech, so an explicit --backend was discarded and another backend's output was written under the user's chosen name, behind a false "unavailable" warning that --explain alone revealed. Refs #121 * fix: verify-round corrections to the Apple Speech backend (#121) Six defects found by the verification legs, four of them capable of producing plausible-but-wrong output rather than an error. Locale resolution read subtags BY POSITION, so the script subtag in zh-Hans was taken for a region, matched nothing, and fell through to the zh preference: a user who asked for Simplified silently received the Traditional model (confirmed by probe). Subtags are now classified by shape, with Hans/Hant mapped to CN/TW and an explicit region still outranking a contradicting script. Provenance recorded the REQUESTED language rather than the one that ran, so a pt-BR request served by pt_PT was labelled pt-BR. The resolved locale is now recorded — hyphenated, because Apple's underscored zh_TW defeats LanguageResolver.baseSubtag (which splits on "-" only) and would have silently disabled the D7 zh script fold for this backend. Recognized text with unusable timing was dropped. The WER denominator is the reference word count and does not move, so a dropped correct word adds a deletion while a dropped garbage word removes an insertion — the measured rate moves in an unsignposted direction. That now fails the run. Whitespace-only segments are kept, since the seam joins with no separator. The locale asset was installed before the audio file was validated, so a typo in the path could cost a multi-hundred-MB download. A nil installation request also fell through to the misleading "Audio format is not supported" this preflight exists to prevent; installedLocales is now re-checked afterwards. The unstructured collector Task was neither awaited nor cancelled when the analyzer threw, pinning the transcriber across a benchmark sweep. aggregateConfidence now treats NaN/inf/out-of-range as unknown rather than passing them through as numbers. Also corrects a claim that exceeded its evidence: "no weight download" appeared in three places while this project itself had to download the ja_JP asset. The accurate claim is about provenance (Apple rather than a model hub), not about network traffic. Refs #121 * docs: declare the Xcode 26 / SDK 26 build floor raised by apple-speech (#121) The package still RUNS on macOS 14, but since #121 it no longer BUILDS on an older toolchain: @available gates the runtime, not the type checker, so Xcode 16 fails on SpeechAnalyzer / SpeechTranscriber. CI is green only because the workflow selects the newest Xcode on the runner (Swift 6.2.4 = Xcode 26); nothing declared the requirement. Refs #121 * merge main into #121 branch Git auto-merged cleanly but silently mis-filed the CHANGELOG: it produced a duplicate '### Fixed' heading and moved the #109 (release sweep) and #111 (provenance fields) entries — both Added items — underneath it. Restructured so Unreleased has one Added (#121, #109, #111), one Fixed (#121 x6, #117, #116/#115, #112) and one Changed (#107). Verified against the post-merge main: 445 tests / 88 suites pass, which covers the semantic interaction CI could not see (#130 changed decodeDeterministic's type after this branch's CI last ran). Refs #121
Refs #118
Refs #120
Summary
Cluster PR — #120 item 1 and #118 are the same code: the extracted helper's return type is the enum #118 settled on. Doing them apart would have written the helper twice.
#118 —
decode_deterministicwasBool?, andnilmeant two different things: "legacy row" and "this backend ignores the flag". The newDecodeDeterminismenum records what we know, not what the backend is:deterministic-enforced--decode-deterministic, and it was onfallback-enabledflag-not-consumedLegacy rows stay on field-absence, which is now unambiguous.
flag-not-consumeddeliberately does not say "this backend is deterministic": we know the flag wasn't consumed, we don't know what happens inside Parakeet / mlx-audio / Apple.#120 item 1 — the honest gate was inline in the benchmark loop, so the "never lie" invariant could not be tested without running a real backend. It's now
DecodeDeterminism.forBackend(_:flagRequested:), a pure function besideflagConsumingBackends, unit-tested across whisperkit / whisper.cpp / mlx-audio / all three Fluid backends / an unknown future backend.#120 item 2 —
--run-kindis now aRunKind: ExpressibleByArgument, so a typo dies at the CLI boundary (exit 64) instead of reaching bench CI.#120 item 3 landed on the bench side (
PsychQuant/bestASR-bench@c93a70e): both provenance fields now accept explicitnullas equivalent to absent, and the value sets are hoisted to module constants.Timing
The migration was free only right now: bench had 156 rows, zero carrying either field, and no sweep had run since #111 merged. The next release sweep starts accumulating rows in whatever representation is current — this deliberately-breaking change (old
true/falseare now rejected) had to land before that.Verification
Independently re-verified by the orchestrator, not taken on the implementer's report:
swift buildclean;swift test396 tests / 83 suites, 0 failures (baseline on this branch is 389 — the implementer corrected my brief, which had carried 402 over from the Bundle A branch that adds 13 tests of its own).RUN_KINDS/DECODE_DETERMINISM. This is the contract neither side can check alone.--run-kind bogus→exit=64,Error: The value 'bogus' is invalid … Please provide one of 'release-sweep' and 'adhoc'; both legal values pass parse.run_kind: nullwas rejected before / accepted after; the enum values were rejected before / accepted after; oldtrue/falsenow rejected; 156 existing rows stillOK.Checklist
bestASR-bench@c93a70e)Generated by /idd-all cluster mode. Do NOT add a GitHub close trailer — IDD discipline requires manual /idd-close after merge.